package login

import (
	. "gitee.com/tomatomeatman/golang-repository/bricks/business/simple/login/model"

	"encoding/json"
	"strings"
	"sync"
	"time"

	Log "github.com/cihub/seelog"

	"gitee.com/tomatomeatman/golang-repository/bricks/model"
	"gitee.com/tomatomeatman/golang-repository/bricks/utils/function/app"
	"gitee.com/tomatomeatman/golang-repository/bricks/utils/function/data"
	"gitee.com/tomatomeatman/golang-repository/bricks/utils/function/file"
	"github.com/gin-gonic/gin"
)

type LoginServer struct {
	app.CommonService
}

var (
	mapLoginUser                    = map[string]LoginUser{} //存储用户登录信息的集合
	dLastClearTime       int64      = 0                      //最后清理的时间戳
	serverSessionTimeout int64      = 600                    //超时时间(秒)
	appMd5Key            string                              //访问密钥
	LoginServerLock      sync.Mutex                          //保存锁
	//sDbName              string                              //数据库名
)

/**
 * 初始化
 */
func init() {
	serverSessionTimeout = app.AppUtil{}.ReadConfigKey("App", "SessionTimeout", serverSessionTimeout).(int64) * 10

	appMd5Key = app.AppUtil{}.ReadConfigKey("App", "Md5Key", "8888").(string)

	//sDbName = app.AppUtil{}.ReadConfigKey("DbVariables", "", "").(string)

	go LoginServer{}.loadCache() //读取缓存
}

/**
 * 登录
 * @param sNameOrNo
 * @param sPass
 * @param sOwner 用户来源表
 * @param iDevice 设备类型,1:PC,2:手机,3:平板,4.....
 * @param iResultInfo 是否返回用户信息
 * @return
 */
func (ls LoginServer) In(ctx *gin.Context, sNameOrNo, sPass, sOwner string, iDevice, iResultInfo int) *model.MsgEmity {
	ls.checkOutTime() //检查超时的登录用户,超时则剔除集合

	if "" == strings.TrimSpace(sNameOrNo) {
		return model.MsgEmity{}.Err(8000, "登录名不能为空！")
	}

	if "" == strings.TrimSpace(sPass) {
		return model.MsgEmity{}.Err(8001, "密码不能为空！")
	}

	if "" == strings.TrimSpace(sOwner) {
		return model.MsgEmity{}.Err(8007, "用户来源不能为空！")
	}

	me := ls.findByNameOrNo(sNameOrNo, sOwner)

	if !me.Gsuccess {
		return model.MsgEmity{}.Err(8002, "查询失败，请稍后重试！")
	}

	list := me.Gdata.([]LoginUser)

	if len(list) < 1 {
		return model.MsgEmity{}.Err(8003, "相关登录名的用户不存在！")
	}

	if len(list) != 1 {
		return model.MsgEmity{}.Err(8004, "存在重名用户，请使用工号登录！")
	}

	loginUser := list[0]
	if (data.Md5{}.Lower(sPass, appMd5Key) != loginUser.GsPass) {
		Log.Debug("用户“", loginUser.GsName, "”登录时密码错误")
		return model.MsgEmity{}.Err(8005, "用户密码错误！")
	}

	if loginUser.GiState != 1 {
		return model.MsgEmity{}.Err(8006, "登录受限，请联系管理员！")
	}

	if iDevice < 1 {
		iDevice = 1
	}

	//LoginLogService{}.AddLog(ctx, sNameOrNo)//AOP已经实现

	//--剔除旧的登录用户--//
	LoginServerLock.Lock() //加锁

	sId := loginUser.GsId
	for key, obj := range mapLoginUser {
		if obj.GsId != sId {
			continue
		}

		if obj.GiDevice != iDevice {
			continue
		}

		delete(mapLoginUser, key)
		Log.Debug("旧登录用户'", sId, "'因同账号登录被剔除登录集合!")
		break
	}

	sCookie := data.Md5{}.Lower(sId, "|", sOwner, "|", time.Now().Unix(), "|", iDevice, "|", appMd5Key)
	loginUser.GsCookie = sCookie //分配的Cookie
	loginUser.GiDevice = iDevice
	loginUser.GsOwner = sOwner
	loginUser.GdLastDate = time.Now().Unix()
	mapLoginUser[sCookie] = loginUser

	LoginServerLock.Unlock() //解锁

	ls.changeCache() //修改缓存

	if iResultInfo != 1 {
		return model.MsgEmity{}.Success(sCookie, "登录成功")
	}

	result := loginUser.Clone()
	result.GsPass = ""     //必须清除
	result.GsSignPass = "" //必须清除

	return model.MsgEmity{}.Success(result, "登录成功")
}

/**
 * 登出
 * @param sCookie
 * @return
 */
func (ls LoginServer) Out(sCookie string) *model.MsgEmity {
	if "" == strings.TrimSpace(sCookie) {
		return model.MsgEmity{}.Err(8002, "令牌参数缺失")
	}

	loginUser, ok := mapLoginUser[sCookie]
	if !ok {
		ls.changeCache() //修改缓存
		return model.MsgEmity{}.Success(8001, "已经登出")
	}

	Log.Debug("登录用户'", loginUser.GsId, "'主动进行了登出操作!")
	ls.checkOutTime() //检查超时的登录用户,超时则剔除集合

	return model.MsgEmity{}.Success(8999, "登出成功")
}

/**
 * 判断sCookie是否已经登录
 * @param sCookie 令牌
 * @return
 */
func (ls LoginServer) Check(sCookie string) *model.MsgEmity {
	if "" == strings.TrimSpace(sCookie) {
		return model.MsgEmity{}.Err(8002, "令牌参数缺失")
	}

	loginUser, ok := mapLoginUser[sCookie]
	if !ok {
		return model.MsgEmity{}.Err(8001, "该登录令牌信息失效")
	}

	loginUser.GdLastDate = time.Now().Unix()

	ls.checkOutTime() //检查超时的登录用户,超时则剔除集合

	return model.MsgEmity{}.Success(8999, "该登录令牌信息有效")
}

/**
 * 登录心跳操作,sCookie存在则更新并返回true,没有则返回false
 * @param sCookie 令牌
 * @return
 */
func (ls LoginServer) Heartbeat(sCookie string) *model.MsgEmity {
	if "" == strings.TrimSpace(sCookie) {
		return model.MsgEmity{}.Err(8002, "令牌参数缺失")
	}

	loginUser, ok := mapLoginUser[sCookie]
	if !ok {
		return model.MsgEmity{}.Err(8001, "令牌已失效,请重新登录")
	}

	loginUser.GdLastDate = time.Now().Unix()

	ls.checkOutTime() //检查超时的登录用户,超时则剔除集合

	return model.MsgEmity{}.Success(8999, "令牌心跳已经更新")
}

/**
 * 取登录信息
 * @param sCookie 令牌
 * @return
 */
func (ls LoginServer) GetLogin(sCookie string) *model.MsgEmity {
	if "" == strings.TrimSpace(sCookie) {
		return model.MsgEmity{}.Err(8002, "令牌参数缺失")
	}

	loginUser, ok := mapLoginUser[sCookie]
	if !ok {
		return model.MsgEmity{}.Err(8001, "令牌已失效,请重新登录")
	}

	loginUser.GdLastDate = time.Now().Unix()

	result := loginUser.Clone()
	result.GsPass = ""     //必须清除
	result.GsSignPass = "" //必须清除

	return model.MsgEmity{}.Success(result, "找到令牌对应的登录信息")
}

/**
 * 根据用户和密码取对应的用户编号
 * @param sNameOrNo
 * @param sPass
 * @param sOwner 用户来源表
 * @return
 */
func (ls LoginServer) GetUserId(sNameOrNo, sPass, sOwner string) *model.MsgEmity {
	if "" == strings.TrimSpace(sNameOrNo) {
		return model.MsgEmity{}.Err(8000, "登录名不能为空！")
	}

	if "" == strings.TrimSpace(sPass) {
		return model.MsgEmity{}.Err(8001, "密码不能为空！")
	}

	if "" == strings.TrimSpace(sOwner) {
		return model.MsgEmity{}.Err(8007, "用户来源不能为空！")
	}

	me := ls.findByNameOrNo(sNameOrNo, sOwner)
	if !me.Gsuccess {
		return model.MsgEmity{}.Err(8002, "查询失败，请稍后重试！")
	}

	list := me.Gdata.([]LoginUser)

	if len(list) < 1 {
		return model.MsgEmity{}.Err(8003, "相关登录名的用户不存在！")
	}

	if len(list) != 1 {
		return model.MsgEmity{}.Err(8004, "存在重名用户，请使用工号登录！")
	}

	loginUser := list[0]
	if (data.Md5{}.Lower(sPass, appMd5Key) == loginUser.GsPass) {
		Log.Debug("用户“", loginUser.GsName, "”登录时密码错误")
		return model.MsgEmity{}.Err(8005, "用户密码错误！")
	}

	if loginUser.GiState != 1 {
		return model.MsgEmity{}.Err(8006, "登录受限，请联系管理员！")
	}

	return model.MsgEmity{}.Success(loginUser.GsId, "查询成功")
}

/**
 * 检查超时的登录用户,超时则剔除集合
 */
func (ls LoginServer) checkOutTime() {
	if dLastClearTime == 0 {
		dLastClearTime = time.Now().Unix()
		return
	}

	//--判断是否需要进行清理,注意:这个算法并不严谨,但判断用户超时并不需要很严谨的进行时刻清理--//
	vNow := time.Now().Unix()
	if (vNow - dLastClearTime) < serverSessionTimeout {
		return
	}

	effectiveMap := map[string]LoginUser{}
	for key, loginUser := range mapLoginUser {
		if (vNow - loginUser.GdLastDate) < serverSessionTimeout {
			effectiveMap[key] = loginUser
			continue
		}

		//delete(mapLoginUser, key)
		Log.Debug("登录用户'", loginUser.GsId, "'因超时将被剔除登录集合!")
	}

	if len(mapLoginUser) == len(effectiveMap) {
		dLastClearTime = time.Now().Unix() //更新最后清理时间
		return
	}

	LoginServerLock.Lock()      //加锁
	mapLoginUser = effectiveMap //替换成有效的
	LoginServerLock.Unlock()    //解锁

	ls.changeCache() //修改缓存

	dLastClearTime = time.Now().Unix() //更新最后清理时间
}

/**
 * 取用户名或工号对应的用户集合
 * @param sNameOrNo
 * @param sOwner
 * @return
 */
func (ls LoginServer) findByNameOrNo(sNameOrNo, sOwner string) *model.MsgEmity {
	return LoginDao{}.FindByNameOrNo(sNameOrNo, sOwner)
}

/**
 * 引入缓存
 */
func (ls LoginServer) loadCache() {
	filePath := "./temp/cache/Login/LoginCache.txt"
	me := file.FileUtil{}.ReadFromFile(filePath)
	if !me.Gsuccess {
		return
	}

	txt := me.Gdata.(string)
	err := json.Unmarshal([]byte(txt), &mapLoginUser)
	if err != nil {
		return
	}
}

/**
 * 修改缓存
 */
func (ls LoginServer) changeCache() {
	if len(mapLoginUser) < 1 {
		return
	}

	ret_json, _ := json.Marshal(mapLoginUser)
	txt := string(ret_json)
	filePath := "./temp/cache/Login/LoginCache.txt"

	go func() {
		LoginServerLock.Lock()              //加锁
		file.FileUtil{}.Save(txt, filePath) //保存文件
		LoginServerLock.Unlock()            //解锁
	}()
}
